var validRegx = function($elem,value,regx,name,callback){
	if(value.match(regx))callback(true,name,value,$elem);else callback(false,name,value,$elem);
}
var checkTypes = {

	"min" : function($elem,value,param,callback){
		//callback(value.length>=parseInt(value),'min',value,$elem);
		return value.length>=parseInt(value);
	},
	"max" : function($elem,value,param,callback){
		//callback(value.length<=parseInt(value),'max',value,$elem);
		return value.length<=parseInt(value);
	},
	'length' : function($elem,value,param,callback){
		var params = param.split(',');
		var len = value.length;
		if(params.length>1){
			if(len<parseInt(params[0]) || len>parseInt(params[1]))return false;//callback(false,'length',value,$elem);
			else return true;//callback(true,'length',value,$elem);
		}else{
			//callback(len<=parseInt(param),'length',value,$elem);
			return len<=parseInt(param);
		}
	},
	'number' : function($elem,value,param,callback){
		//callback(value.match(/^[+\-]?\d+(?:.\d+)$/g),'number',value,$elem);
		return value.match(/^[+\-]?\d+(?:.\d+)$/g);
	},
	'int' : function($elem,value,param,callback){
		//callback(value.match(/^[+\-]?\d+$/g),'uint',value,$elem);
		return value.match(/^[+\-]?\d+$/g);
	},
	'uint' : function($elem,value,param,callback){
		//callback(value.match(/^\d+$/g),'uint',value,$elem);
		return value.match(/^\d+$/g);
	},
	'range' : function($elem,value,param,callback){
		var params = param.split(',');
		var len = parseInt(value);
		if(params.length>1){
			if(len<parseInt(params[0])||len>parseInt(params[1]))return false;//callback(false,'range',value,$elem);
			//if()callback(false,'range',value,$elem);
			else return true;//callback(true,'range',value,$elem);
		}else{
			//callback(len<=parseInt(param),'range',value,$elem);
			return len<=parseInt(param);
		}
	},
	'email' : function($elem,value,param,callback){
		var regx = /^[\w\-]+(?:\.[\w\-]+)?@(?:(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5]))|(?:[\w\-]+\.)+[a-zA-Z]{2,4})$/gi;
		
		var ret = value.match(regx);
		return ret;
	},
	'chinese' : function($elem,value,param,callback){
		var regx = /^[\u4E00-\u9FA5]+$/gi;
		//callback(value.match(regx),'chinese',value,$elem);
		return value.match(regx);
	},
	'url' : function($elem,value,param,callback){
		//var regx = /^(http(s)?://)?([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?$/gi;
		var regx = /[a-z]/gi;
		//callback(value.match(regx),'url',value,$elem);
		return true;
	},
	'ipv4' : function($elem,value,param,callback){
		var regx = /^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$/gi;
		//callback(value.match(regx),'ipv4',value,$elem);
		return value.match(regx);
	},
	'date' : function($elem,value,param,callback){
		var regx = /^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/gi;
		//callback(value.match(regx),'date',value,$elem);
		return value.match(regx);
	},
	'date1' : function($elem,value,param,callback){
		//var regx = /^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/gi;
		var regx = /[a-z]/gi;
		//callback(value.match(regx),'date1',value,$elem);
		return true;
	},
	'guid' : function(){
		var regx = /(?:\{[A-F0-9]{8}(-[A-F0-9]{4}){3}-[A-F0-9]{12}\})|(?:[A-F0-9]{8}(-[A-F0-9]{4}){3}-[A-F0-9]{12})/gi;
		//callback(value.match(regx),'guid',value,$elem);
		return value.match(regx);
	},

	'regx' : function($elem,value,param,callback){
		//callback(value.match(new RegExp(param)),'email',value,$elem);
		return value.match(regx);
	}
}
var val_handlers = {
	"trim" : function(val){return $.trim(val);}
}
var checkRemote = function($elem,value,param,callback){
	var paramIndex = params.indexOf(",");
	var method = "GET",url = param;
	if(paramIndex>=0){
		method = param.substr(0,paramIndex);
		url = param.substring(paramIndex+1);
	}
	var url = url + value;
	if(url.indexOf("?")>=0)url+= "&" + value;
	else url += "?"+value;
	$.ajax(url + "&"+Math.random(),{
		type : method,
		success : function(__$VALIDAJAXRESULT__){
			if(__$VALIDAJAXRESULT__==="true"){return callback(true,'remote',value,$elem);}
			try{
				"__$VALIDAJAXRESULT__=" + __$VALIDAJAXRESULT__;var ret = __$VALIDAJAXRESULT__;
				var rs = ret.status==='ok';
				if(rs===undefined) rs = ret.success;
				if(rs===undefined) rs = !ret.error;
				callback(rs);
			}catch(ex){
				return callback(null);
			}
		}
	});
}

var genGetValue=function(elems,valHandler){
	var injectGetVal = function($elem){
		var tag = $elem.attr('tagName'),elem = $elem[0];
		var t = $elem.attr('type');
		if(tag=='SELECT') $elem.__$$VALIDGETVAL__ = function(){
			return elem.options[elem.selectedIndex].value;
		};else if(t=='checkbox'||t=='radio')	$elem.__$$VALIDGETVAL__ = function(){
			return (elem.checked || elem.getAttribute('checked'))?$elem.val():null;
		};else $elem.__$$VALIDGETVAL__ = function(){return $elem.val();}
	}
	var getValue;
	if(elems.length==1){
		var $elem = elems[0];
		injectGetVal($elem);
		if(!valHandler)getValue = function(){return $elem.__$$VALIDGETVAL__();}
		else getValue = function(){return valHandler($elem.__$$VALIDGETVAL__()||"");}
	}else{
		for(var i in elems) injectGetVal(elems[i]);
		if(!valHandler){
			getValue = function(){
				var vals = [];
				for(var i in elems){var val = elems[i].__$$VALIDGETVAL__();if(val!==null)vals.push(val);}
				return vals.join(',');
			}
		}else{
			getValue = function(){
				var vals = [];
				for(var i in elems){var val = elems[i].__$$VALIDGETVAL__();if(val!==null)vals.push(valHandler(val));}
				return vals.join(',');
			}
		}
			
	}
	return getValue;
}

var parseParam = function(param){
	var argIndex = param.indexOf('|');
	var msg;
	if(argIndex>=0){
		if(argIndex<param.length)msg = param.substring(argIndex+1);
		param = param.substr(0,argIndex);
	}else{
		msg = param;
	}
	return {param : param,msg :msg};
}

var inputValidator = function($elem,opts,$form){
	var $tip =$elem.parent().find("*[data-valid-for='"+$elem.attr('name')+"']");
	if($tip.length==0)$tip = null;
	var cssValidate = opts.validateCss|| 'valid-validation',cssSuccess = opts.successCss || 'valid-ok',cssError = opts.errorCss || "valid-error",cssValidating = opts.validatingCss || "valid-validating";
	var statusCss = "";
	var successText=$elem.attr('data-valid-success') || $form.attr('data-valid-success') || opts.successText;
	var self = this;
	var tick;
	var check = function(){
		var src = this;
		if(tick)clearTimeout(tick);
		tick=setTimeout(function(){tick = 0;self.validate(null,src);},200);
	};
	var elems=[];
	this.append = function($elem){
		elems.push($elem);
		$elem[0].__$$VALIDATION__ = self;
		$elem.blur(check).keyup(check).change(check).addClass(cssValidate);
		if($elem.attr('tagName')==='INPUT' && $elem.attr('type')=='text') $elem.keypress(function(evt){
			if(evt.keyCode==13){$elem.blur();return false;}
		}).focus(function(){if(statusCss)setCss('');});
	}
	
	var setCss = function(css,msg){
		if(elems.length==1){
			setCss = function(css,msg){
				if(statusCss){$elem.removeClass(statusCss);if($tip){$tip.removeClass(statusCss);}}
				if(statusCss =css){ $elem.addClass(css);if($tip){$tip.addClass(css);$tip.html(msg||"");}}
			}
		}else{
			setCss = function(css,msg){
				if(statusCss){for(var i in elems)elems[i].removeClass(statusCss);if($tip){$tip.removeClass(statusCss);}}
				if(statusCss = css){for(var i in elems)elems[i].addClass(css);if($tip){$tip.addClass(css);$tip.html(msg||"");}}
			}
		}
		setCss(css,msg);
	}
	
	var getTrueValue = function(){getTrueValue= genGetValue(elems,val_handlers[$elem.attr('data-valid-value')]); return getTrueValue();}

	var validations = {},valid_count=0;
	var required= $elem.attr('data-valid-required');
	var compare = $elem.attr('data-valid-compare');
	if(compare){
		var cparams = parseParam(compare);
		var $compareElem;
		if(cparams.param.indexOf('#')==0){
			$compareElem = $(cparams.param);
		}else{
			if($form) $compareElem = $(cparams.param,$form);
		}
		if($compareElem){
			var validation = {
				type : 'compare',
				func : function(){
					return $compareElem.val() == $elem.val();
				},
				param : cparams.param,
				msg : cparams.msg
			};
			validations['compare'] = validation;
			valid_count++;
		}
	}
	
	for(var n in checkTypes){
		var param = $elem.attr('data-valid-'+n);
		if(param){
			var params = parseParam(param);
			var validation = {
				type : n,
				func : checkTypes[n],
				param : params.param,
				msg : params.msg
			};
			validations[n] = validation;
			valid_count++;
		}
	}
	var remoteParam =  $elem.attr('data-valid-remote');
	if(!(required || valid_count|| remoteParam)) {
		this.validate = function(callback){if(callback)callback.call($elem,null);return true;}
		return;
	}
	this.lived = true;
	this.append($elem);
	
	var normalValidate = function(value,callback,src){
		var errors = {},error_msg;
		if(value===''){
			if(required) {
				setCss(cssError,required);
				if(opts.onError)opts.onError.call($elem,errors={'required':required},value,src);
				return errors;
			}else{
				setCss(cssSuccess,successText);
				if(opts.onSuccess)opts.onSuccess.call($elem,value,src);
				return;
			}
		}
		
		for(var n in validations){
			var validation = validations[n];
			if(!validation.func($elem,value,validation.param)){error_msg = errors[n] = validation.msg;break;}
		}
		if(error_msg!==undefined){setCss(cssError,error_msg);if(opts.onError)opts.onError.call($elem,errors,value,src);if(callback)callback.call($elem,errors,value,src);return errors;}
		return;
	}
	this.validate = function(callback,src){
		var self = $elem,undefined;
		if(!remoteParam){
			this.validate = function(callback,src){
				if(tick) clearTimeout(tick);
				var value = getTrueValue();
				var result = normalValidate(value,callback,src);
				if(result===undefined){
					setCss(cssSuccess,successText);
					if(opts.onSuccess)opts.onSuccess.call($elem,value,src);
					if(callback)callback.call($elem,undefined,value,src);
				}else{
					if(callback)callback.call($elem,result,value,src);
				}
				return result===undefined;
			}
		}else{
			var params = parseParam(remoteParam);
			var msg;
			if(argIndex>=0){
				if(argIndex<remoteParam.length)msg = remoteParam.substring(argIndex+1);
				remoteParam = remoteParam.substr(0,argIndex);
			}
			this.validate = function(callback,src){
				if(tick) clearTimeout(tick);
				var value = getTrueValue();
				var errors = {},error_msg;
				var result = normalValidate(value,callback,src);
				if(result!==undefined) {if(callback)callback.call($elem,result,value,src); return false;}
				setCss(cssValidating,"");
				checkRemote($elem,value,params.param,function(ok){
					if(!ok){
						setCss(cssError,param.msg);
						var errors={'remote':param.msg};
						if(opts.onError)opts.onError.call($elem,errors,value,src);
						if(callback)callback.call($elem,errors,value,src);return false;
					}else{
						setCss(cssSuccess,successText);
						if(opts.onSuccess)opts.onSuccess.call($elem,value,src);
						if(callback)callback.call($elem,undefined,value,src);return true;
					}
				});
				return error_msg===undefined;
			}
		}
		return this.validate(callback,src);
	}
}
var groupValidator = function($form,elems,opts){
	var length = elems.length;
	var validators ={},valid_count=0;
	for(var i=0;i<length;i++){
		var $elem = $(elems[i]);
		var n = $elem.attr('name');
		var exists = validators[n];
		if(exists){ exists.append($elem);continue;}
		var validator = new inputValidator($elem,opts,$form);
		if(validator.lived){validators[n]= validator;valid_count++;}
	}
	if(valid_count){
		this.validate = function(callback){
			var errors={},error_count=0,task_count = valid_count;
			for(var n in validators){
				var validator = validators[n];
				validator.validate(function(error,value,src){
					if(error){ errors[this.attr('name')] = error;error_count++;}
					if(--task_count==0 && callback){
						callback(error_count?errors:undefined);
					}
				});
			}
			if(error_count) return false;
			if(task_count==0)return true;
		}
	}else{
		this.validate = function(callback){if(callback)callback(null);return true;}
	}
	
}
$.fn.validate = function(opts){
	var validation;
	if(validation = this[0].__$$VALIDATION__){return validation.validate(typeof(opts)==='function'?opts:null);}
	opts = $.extend({},$.fn.validate.opts,opts);
	var tag = this.attr("tagName");
	if(tag==='INPUT' || tag==='SELECT' || tag==='TEXTAREA'){
		
		var validator = this[0].__$$VALIDATION__ = new inputValidator(this,opts);
		return this;
	}else{
		var elems;
		if(tag!=='FORM'){
			elems = $('input,select,textarea',this);
		}else elems = this[0].elements;
		var successText = this.attr('data-valid-success');
		var validator = this[0].__$$VALIDATION__ = new groupValidator(this,elems,opts);
		return this;
	}
}
$.fn.validate.opts ={
	errorCss : "valid-error",
	successCss : 'valid-success',
	validationCss : 'valid-validation',
	validatingCss : 'valid-validating',
	successText : '',
	onSucess : null,
	onError : null
}